/* ***********************************************************************
**
**  Copyright (C) 2006  Jesper Hansen <jesper@redegg.net>
**
**
**  Interface functions for MMC/SD cards
**
**  File mmc_if.h
**
*************************************************************************
**
**  This program is free software; you can redistribute it and/or
**  modify it under the terms of the GNU General Public License
**  as published by the Free Software Foundation; either version 2
**  of the License, or (at your option) any later version.
**
**  This program is distributed in the hope that it will be useful,
**  but WITHOUT ANY WARRANTY; without even the implied warranty of
**  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
**  GNU General Public License for more details.
**
**  You should have received a copy of the GNU General Public License
**  along with this program; if not, write to the Free Software Foundation,
**  Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
**
*************************************************************************/

#include <avr/io.h>
#include <inttypes.h>
#include <stdio.h>
#include "mmc_if.h"


uint8_t spi_byte(uint8_t byte)
{
    SPDR = byte;
    while(!(SPSR & (1<<SPIF)))
    {}
    return SPDR;
}



void mmc_send_command(uint8_t command, uint16_t px, uint16_t py)
{
    register union u16convert r;

    MMC_CS_PORT &= ~(1 << MMC_CS);  // enable CS

    spi_byte(0xff);         // dummy byte

    spi_byte(command | 0x40);

    r.value = px;
    spi_byte(r.bytes.high); // high byte of param x
    spi_byte(r.bytes.low);  // low byte of param x

    r.value = py;
    spi_byte(r.bytes.high); // high byte of param y
    spi_byte(r.bytes.low);  // low byte of param y

    spi_byte(0x95);         // correct CRC for first command in SPI
                            // after that CRC is ignored, so no problem with
                            // always sending 0x95
    spi_byte(0xff);         // ignore return byte
}


uint8_t mmc_get(void)
{
    uint16_t i = 0xffff;
    uint8_t b = 0xff;

    while ((b == 0xff) && (--i))
    {
        b = spi_byte(0xff);
    }
    return b;

}

uint8_t mmc_datatoken(void)
{
    uint16_t i = 0xffff;
    uint8_t b = 0xff;

    while ((b != 0xfe) && (--i))
    {
        b = spi_byte(0xff);
    }
    return b;
}


void mmc_clock_and_release(void)
{
    uint8_t i;

    // SD cards require at least 8 final clocks
    for(i=0;i<10;i++)
        spi_byte(0xff);

    MMC_CS_PORT |= (1 << MMC_CS);   // release CS
}



int mmc_readsector(uint32_t lba, uint8_t *buffer)
{
    uint16_t i;

    // send read command and logical sector address
    mmc_send_command(17,(lba>>7) & 0xffff, (lba<<9) & 0xffff);

    if (mmc_datatoken() != 0xfe)    // if no valid token
    {
        mmc_clock_and_release();    // cleanup and
        return -1;                  // return error code
    }

    for (i=0;i<512;i++)             // read sector data
        *buffer++ = spi_byte(0xff);

    spi_byte(0xff);                 // ignore dummy checksum
    spi_byte(0xff);                 // ignore dummy checksum

    mmc_clock_and_release();        // cleanup

    return 0;                       // return success
}





uint8_t mmc_init(void)
{
    int i;


    // setup I/O ports

    SPI_PORT &= ~((1 << MMC_SCK) | (1 << MMC_MOSI));    // low bits
    SPI_PORT |= (1 << MMC_MISO);                        // high bits
    SPI_DDR  |= (1<<MMC_SCK) | (1<<MMC_MOSI);           // direction


    MMC_CS_PORT |= (1 << MMC_CS);   // Initial level is high
    MMC_CS_DIR  |= (1 << MMC_CS);   // Direction is output


    // also need to set SS as output
#if defined(__AVR_ATmega8__)
    // is already set as CS, but we set it again to accomodate for other boards
    SPI_DDR |= (1<<2);
#else
    SPI_DDR |= (1<<0);          // assume it's bit0 (mega128, portB and others)
#endif

    SPCR = (1<<MSTR)|(1<<SPE);  // enable SPI interface
    SPSR = 1;                   // set double speed

    for(i=0;i<10;i++)           // send 80 clocks while card power stabilizes
        spi_byte(0xff);

    mmc_send_command(0,0,0);    // send CMD0 - reset card

    if (mmc_get() != 1)         // if no valid response code
    {
       mmc_clock_and_release();
       return 1;                // card cannot be detected
    }

    //
    // send CMD1 until we get a 0 back, indicating card is done initializing
    //
    i = 0xffff;                     // max timeout
    while ((spi_byte(0xff) != 0) && (--i))  // wait for it
    {
         mmc_send_command(1,0,0);   // send CMD1 - activate card init
    }

    mmc_clock_and_release();        // clean up

    if (i == 0)                     // if we timed out above
       return 2;                    // return failure code

    return 0;
}
